#!/usr/bin/env python3
"""
PCAP to JSON Converter
Converts PCAP files to JSON format for easier analysis and processing.

Usage:
    python3 pcap-to-json.py <input.pcap> [output.json]
    python3 pcap-to-json.py --help

Features:
    - Converts PCAP files to structured JSON
    - Extracts packet details including timestamps, source, destination, protocols
    - Supports filtering by protocol type
    - Handles SCTP, TCP, UDP, and other protocols
    - Provides summary statistics
"""

import json
import subprocess
import sys
import argparse
import re
from datetime import datetime
from pathlib import Path
import logging

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

class PCAPToJSON:
    def __init__(self):
        self.packets = []
        self.stats = {
            'total_packets': 0,
            'protocols': {},
            'source_ips': {},
            'dest_ips': {},
            'ports': {}
        }
    
    def parse_tcpdump_line(self, line):
        """Parse a single tcpdump line into a structured packet object"""
        try:
            # Basic regex pattern for tcpdump output
            # Format: timestamp interface direction IP src.port > dst.port: protocol details
            pattern = r'(\d{2}:\d{2}:\d{2}\.\d{6})\s+(\w+)\s+(In|Out)\s+IP\s+([\d\.]+)\.(\d+)\s*>\s*([\d\.]+)\.(\d+):\s*(.+)'
            match = re.match(pattern, line.strip())
            
            if not match:
                return None
            
            timestamp, interface, direction, src_ip, src_port, dst_ip, dst_port, details = match.groups()
            
            packet = {
                'timestamp': timestamp,
                'interface': interface,
                'direction': direction,
                'source': {
                    'ip': src_ip,
                    'port': int(src_port)
                },
                'destination': {
                    'ip': dst_ip,
                    'port': int(dst_port)
                },
                'details': details,
                'protocol': self._extract_protocol(details),
                'size': self._extract_packet_size(details)
            }
            
            return packet
            
        except Exception as e:
            logger.warning(f"Failed to parse line: {line.strip()} - {e}")
            return None
    
    def _extract_protocol(self, details):
        """Extract protocol from packet details"""
        protocols = ['SCTP', 'TCP', 'UDP', 'ICMP', 'ARP']
        for protocol in protocols:
            if protocol in details.upper():
                return protocol
        return 'Unknown'
    
    def _extract_packet_size(self, details):
        """Extract packet size from details"""
        size_match = re.search(r'length (\d+)', details)
        if size_match:
            return int(size_match.group(1))
        return None
    
    def update_stats(self, packet):
        """Update statistics with packet information"""
        self.stats['total_packets'] += 1
        
        # Protocol stats
        protocol = packet['protocol']
        self.stats['protocols'][protocol] = self.stats['protocols'].get(protocol, 0) + 1
        
        # IP stats
        src_ip = packet['source']['ip']
        dst_ip = packet['destination']['ip']
        self.stats['source_ips'][src_ip] = self.stats['source_ips'].get(src_ip, 0) + 1
        self.stats['dest_ips'][dst_ip] = self.stats['dest_ips'].get(dst_ip, 0) + 1
        
        # Port stats
        src_port = packet['source']['port']
        dst_port = packet['destination']['port']
        self.stats['ports'][f"{src_ip}:{src_port}"] = self.stats['ports'].get(f"{src_ip}:{src_port}", 0) + 1
        self.stats['ports'][f"{dst_ip}:{dst_port}"] = self.stats['ports'].get(f"{dst_ip}:{dst_port}", 0) + 1
    
    def convert_pcap(self, input_file, output_file=None, protocol_filter=None):
        """Convert PCAP file to JSON format"""
        input_path = Path(input_file)
        
        if not input_path.exists():
            raise FileNotFoundError(f"Input file not found: {input_file}")
        
        if output_file is None:
            output_file = input_path.with_suffix('.json')
        
        logger.info(f"Converting {input_file} to {output_file}")
        
        # Build tcpdump command
        cmd = ['tcpdump', '-r', str(input_path), '-n']
        
        if protocol_filter:
            cmd.extend(['-i', 'any', protocol_filter])
        
        try:
            # Run tcpdump
            result = subprocess.run(cmd, capture_output=True, text=True, check=True)
            lines = result.stdout.strip().split('\n')
            
            logger.info(f"Processing {len(lines)} packet lines")
            
            # Parse each line
            for line in lines:
                if line.strip():
                    packet = self.parse_tcpdump_line(line)
                    if packet:
                        self.packets.append(packet)
                        self.update_stats(packet)
            
            # Create output structure
            output_data = {
                'metadata': {
                    'input_file': str(input_path),
                    'output_file': str(output_file),
                    'conversion_time': datetime.now().isoformat(),
                    'total_packets': len(self.packets)
                },
                'statistics': self.stats,
                'packets': self.packets
            }
            
            # Write JSON file
            with open(output_file, 'w') as f:
                json.dump(output_data, f, indent=2)
            
            logger.info(f"Successfully converted {len(self.packets)} packets to {output_file}")
            return output_file
            
        except subprocess.CalledProcessError as e:
            logger.error(f"tcpdump command failed: {e}")
            raise
        except Exception as e:
            logger.error(f"Conversion failed: {e}")
            raise
    
    def filter_by_protocol(self, protocol):
        """Filter packets by protocol"""
        return [p for p in self.packets if p['protocol'].upper() == protocol.upper()]
    
    def filter_by_ip(self, ip):
        """Filter packets by IP address"""
        return [p for p in self.packets if p['source']['ip'] == ip or p['destination']['ip'] == ip]
    
    def get_summary(self):
        """Get summary of the conversion"""
        return {
            'total_packets': len(self.packets),
            'protocols': self.stats['protocols'],
            'top_source_ips': dict(sorted(self.stats['source_ips'].items(), key=lambda x: x[1], reverse=True)[:5]),
            'top_dest_ips': dict(sorted(self.stats['dest_ips'].items(), key=lambda x: x[1], reverse=True)[:5])
        }

def main():
    parser = argparse.ArgumentParser(description='Convert PCAP files to JSON format')
    parser.add_argument('input_file', help='Input PCAP file path')
    parser.add_argument('output_file', nargs='?', help='Output JSON file path (optional)')
    parser.add_argument('--protocol', '-p', help='Filter by protocol (SCTP, TCP, UDP, etc.)')
    parser.add_argument('--summary', '-s', action='store_true', help='Show summary only')
    parser.add_argument('--verbose', '-v', action='store_true', help='Verbose output')
    
    args = parser.parse_args()
    
    if args.verbose:
        logging.getLogger().setLevel(logging.DEBUG)
    
    try:
        converter = PCAPToJSON()
        output_file = converter.convert_pcap(args.input_file, args.output_file, args.protocol)
        
        if args.summary:
            summary = converter.get_summary()
            print(json.dumps(summary, indent=2))
        else:
            print(f"Conversion completed: {output_file}")
            print(f"Total packets: {len(converter.packets)}")
            print(f"Protocols: {list(converter.stats['protocols'].keys())}")
        
    except Exception as e:
        logger.error(f"Error: {e}")
        sys.exit(1)

if __name__ == '__main__':
    main() 